﻿/*************************************************************************************

   Extended WPF Toolkit

   Copyright (C) 2007-2013 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license

   For more features, controls, and fast professional support,
   pick up the Plus Edition at http://xceed.com/wpf_toolkit

   Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids

  ***********************************************************************************/

using ICodeShare.UI.Controls.Primitives;
using ICodeShare.UI.Controls.Utilities;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

#if VS2008
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
#endif

namespace ICodeShare.UI.Controls
{
    [TemplatePart(Name = PART_Calendar, Type = typeof(Calendar))]
    [TemplatePart(Name = PART_TimeUpDown, Type = typeof(TimePicker))]
    public class DateTimePicker : DateTimePickerBase
    {
        private const string PART_Calendar = "PART_Calendar";
        private const string PART_TimeUpDown = "PART_TimeUpDown";

        #region Members

        private Calendar _calendar;
        private TimePicker _timePicker;
        private DateTime? _calendarTemporaryDateTime;
        private DateTime? _calendarIntendedDateTime;

        #endregion //Members

        #region Properties

        #region AutoCloseCalendar

        public static readonly DependencyProperty AutoCloseCalendarProperty = DependencyProperty.Register("AutoCloseCalendar", typeof(bool), typeof(DateTimePicker), new UIPropertyMetadata(false));
        public bool AutoCloseCalendar
        {
            get
            {
                return (bool)GetValue(AutoCloseCalendarProperty);
            }
            set
            {
                SetValue(AutoCloseCalendarProperty, value);
            }
        }

        #endregion //AutoCloseCalendar

        #region CalendarDisplayMode

        public static readonly DependencyProperty CalendarDisplayModeProperty = DependencyProperty.Register("CalendarDisplayMode", typeof(CalendarMode)
          , typeof(DateTimePicker), new UIPropertyMetadata(CalendarMode.Month));
        public CalendarMode CalendarDisplayMode
        {
            get
            {
                return (CalendarMode)GetValue(CalendarDisplayModeProperty);
            }
            set
            {
                SetValue(CalendarDisplayModeProperty, value);
            }
        }

        #endregion //CalendarDisplayMode

        #region TimeFormat

        public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register("TimeFormat", typeof(DateTimeFormat), typeof(DateTimePicker), new UIPropertyMetadata(DateTimeFormat.ShortTime));
        public DateTimeFormat TimeFormat
        {
            get
            {
                return (DateTimeFormat)GetValue(TimeFormatProperty);
            }
            set
            {
                SetValue(TimeFormatProperty, value);
            }
        }

        #endregion //TimeFormat

        #region TimeFormatString

        public static readonly DependencyProperty TimeFormatStringProperty = DependencyProperty.Register("TimeFormatString", typeof(string), typeof(DateTimePicker), new UIPropertyMetadata(default(String)), IsTimeFormatStringValid);
        public string TimeFormatString
        {
            get
            {
                return (string)GetValue(TimeFormatStringProperty);
            }
            set
            {
                SetValue(TimeFormatStringProperty, value);
            }
        }

        private static bool IsTimeFormatStringValid(object value)
        {
            return DateTimeUpDown.IsFormatStringValid(value);
        }

        #endregion //TimeFormatString

        #region TimePickerAllowSpin

        public static readonly DependencyProperty TimePickerAllowSpinProperty = DependencyProperty.Register("TimePickerAllowSpin", typeof(bool), typeof(DateTimePicker), new UIPropertyMetadata(true));
        public bool TimePickerAllowSpin
        {
            get
            {
                return (bool)GetValue(TimePickerAllowSpinProperty);
            }
            set
            {
                SetValue(TimePickerAllowSpinProperty, value);
            }
        }

        #endregion //TimePickerAllowSpin

        #region TimePickerShowButtonSpinner

        public static readonly DependencyProperty TimePickerShowButtonSpinnerProperty = DependencyProperty.Register("TimePickerShowButtonSpinner", typeof(bool), typeof(DateTimePicker), new UIPropertyMetadata(true));
        public bool TimePickerShowButtonSpinner
        {
            get
            {
                return (bool)GetValue(TimePickerShowButtonSpinnerProperty);
            }
            set
            {
                SetValue(TimePickerShowButtonSpinnerProperty, value);
            }
        }

        #endregion //TimePickerShowButtonSpinner

        #region TimePickerVisibility

        public static readonly DependencyProperty TimePickerVisibilityProperty = DependencyProperty.Register("TimePickerVisibility", typeof(Visibility), typeof(DateTimePicker), new UIPropertyMetadata(Visibility.Visible));
        public Visibility TimePickerVisibility
        {
            get
            {
                return (Visibility)GetValue(TimePickerVisibilityProperty);
            }
            set
            {
                SetValue(TimePickerVisibilityProperty, value);
            }
        }

        #endregion //TimePickerVisibility

        #region TimeWatermark

        public static readonly DependencyProperty TimeWatermarkProperty = DependencyProperty.Register("TimeWatermark", typeof(object), typeof(DateTimePicker), new UIPropertyMetadata(null));
        public object TimeWatermark
        {
            get
            {
                return (object)GetValue(TimeWatermarkProperty);
            }
            set
            {
                SetValue(TimeWatermarkProperty, value);
            }
        }

        #endregion //TimeWatermark

        #region TimeWatermarkTemplate

        public static readonly DependencyProperty TimeWatermarkTemplateProperty = DependencyProperty.Register("TimeWatermarkTemplate", typeof(DataTemplate), typeof(DateTimePicker), new UIPropertyMetadata(null));
        public DataTemplate TimeWatermarkTemplate
        {
            get
            {
                return (DataTemplate)GetValue(TimeWatermarkTemplateProperty);
            }
            set
            {
                SetValue(TimeWatermarkTemplateProperty, value);
            }
        }

        #endregion //TimeWatermarkTemplate

        #endregion //Properties

        #region Constructors

        static DateTimePicker()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DateTimePicker), new FrameworkPropertyMetadata(typeof(DateTimePicker)));
        }

        public DateTimePicker()
        {
        }

        #endregion //Constructors

        #region Base Class Overrides

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_calendar != null)
                _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;

            _calendar = GetTemplateChild(PART_Calendar) as Calendar;

            if (_calendar != null)
            {
                _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
                _calendar.SelectedDate = Value ?? null;
                _calendar.DisplayDate = Value ?? this.ContextNow;
                this.SetBlackOutDates();
            }

            _timePicker = GetTemplateChild(PART_TimeUpDown) as TimePicker;
            _timePicker.ValueChanged += _timePicker_ValueChanged;
        }

        private void _timePicker_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            e.Handled = true;

            // if UpdateValueOnEnterKey is true, 
            // Sync Value on Text only when Enter Key is pressed.
            if (this.UpdateValueOnEnterKey)
            {
                var newTime = e.NewValue as DateTime?;
                if (newTime != null)
                {
                    _fireSelectionChangedEvent = false;
                    var currentDate = this.ConvertTextToValue(this.TextBox.Text);
                    var date = currentDate ?? this.ContextNow;
                    var newValue = new DateTime(date.Year, date.Month, date.Day, newTime.Value.Hour, newTime.Value.Minute, newTime.Value.Second, newTime.Value.Millisecond, date.Kind);
                    this.TextBox.Text = newValue.ToString(this.GetFormatString(this.Format), this.CultureInfo);
                    _fireSelectionChangedEvent = true;
                }
            }
        }

        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
        {
            if (Mouse.Captured is CalendarItem)
            {
                Mouse.Capture(null);

                if (AutoCloseCalendar && (_calendar.DisplayMode == CalendarMode.Month))
                {
                    ClosePopup(true);
                }
            }
            base.OnPreviewMouseUp(e);
        }

        protected override void OnValueChanged(DateTime? oldValue, DateTime? newValue)
        {
            //The calendar only select the Date part, not the time part.
            DateTime? newValueDate = (newValue != null)
              ? newValue.Value.Date
              : (DateTime?)null;

            if (_calendar != null && _calendar.SelectedDate != newValueDate)
            {
                _calendar.SelectedDate = newValueDate;
                _calendar.DisplayDate = newValue.GetValueOrDefault(this.ContextNow);
            }

            //If we change any part of the datetime without
            //using the calendar when the actual date is temporary,
            //clear the temporary value. 
            if (_calendarTemporaryDateTime != null &&
              newValue != _calendarTemporaryDateTime)
            {
                _calendarTemporaryDateTime = null;
                _calendarIntendedDateTime = null;
            }

            base.OnValueChanged(oldValue, newValue);
        }

        protected override void OnIsDropDownOpenChanged(bool oldValue, bool newValue)
        {
            base.OnIsDropDownOpenChanged(oldValue, newValue);

            if (!newValue)
            {
                _calendarTemporaryDateTime = null;
                _calendarIntendedDateTime = null;
            }
        }

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            //if the calendar is open then we don't want to modify the behavior of navigating the calendar control with the Up/Down keys.
            if (!IsDropDownOpen)
                base.OnPreviewKeyDown(e);
        }

        protected override void OnMaximumChanged(DateTime? oldValue, DateTime? newValue)
        {
            base.OnMaximumChanged(oldValue, newValue);

            this.SetBlackOutDates();
        }

        protected override void OnMinimumChanged(DateTime? oldValue, DateTime? newValue)
        {
            base.OnMinimumChanged(oldValue, newValue);

            this.SetBlackOutDates();
        }

        #endregion //Base Class Overrides

        #region Event Handlers

        protected override void HandleKeyDown(object sender, KeyEventArgs e)
        {
            // The base call will handle the Ctrl+Down, Enter and Esc keys
            // in order to open o r close the popup.
            //Do not close the Calendar if the call is handled
            //by the TimePicker inside the DateTimePicker template
            if (IsDropDownOpen
              && _timePicker.IsKeyboardFocusWithin
              && (_timePicker.IsDropDownOpen || e.Handled))
                return;

            base.HandleKeyDown(sender, e);
        }

        private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count > 0)
            {
                var newDate = (DateTime?)e.AddedItems[0];

                if (newDate != null)
                {
                    //The Calendar will always return a date with an "Unspecified" Kind.
                    //Force the expected kind to the value.
                    newDate = DateTime.SpecifyKind(newDate.Value, this.Kind);

                    // Only change the year, month, and day part of the value. Keep everything to the last "tick."
                    // "Milliseconds" aren't precise enough. Use a mathematical scheme instead.
                    if (_calendarIntendedDateTime != null)
                    {
                        newDate = newDate.Value.Date + _calendarIntendedDateTime.Value.TimeOfDay;
                        _calendarTemporaryDateTime = null;
                        _calendarIntendedDateTime = null;
                    }
                    else if (Value != null)
                    {
                        newDate = newDate.Value.Date + Value.Value.TimeOfDay;
                    }

                    // Always be sure that the time part of the selected value is always 
                    // within the bound of the min max. The time part could be altered
                    // if the calendar's selected date match the Minimum or Maximum date.
                    // Keep in memory the intended time of day, in case that the selected
                    // calendar date is only transitory (browsing the calendar with the keyboard)
                    var limitedDateTime = this.GetClippedMinMaxValue(newDate);

                    if (limitedDateTime.Value != newDate.Value)
                    {
                        _calendarTemporaryDateTime = limitedDateTime;
                        _calendarIntendedDateTime = newDate;
                        newDate = limitedDateTime;
                    }
                }

                if (this.UpdateValueOnEnterKey)
                {
                    _fireSelectionChangedEvent = false;
                    this.TextBox.Text = newDate.Value.ToString(this.GetFormatString(this.Format), this.CultureInfo);
                    _fireSelectionChangedEvent = true;
                }
                else
                {
                    if (!object.Equals(newDate, Value))
                    {
                        this.Value = newDate;
                    }
                }
            }
        }

        protected override void Popup_Opened(object sender, EventArgs e)
        {
            base.Popup_Opened(sender, e);

            if (_calendar != null)
                _calendar.Focus();
        }

        #endregion //Event Handlers

        #region Methods

        private void SetBlackOutDates()
        {
            if (_calendar != null)
            {
                _calendar.BlackoutDates.Clear();

                if ((this.Minimum != null) && this.Minimum.HasValue && (this.Minimum.Value != DateTime.MinValue))
                {
                    DateTime minDate = this.Minimum.Value;
                    _calendar.BlackoutDates.Add(new CalendarDateRange(DateTime.MinValue, minDate.AddDays(-1)));
                }
                if ((this.Maximum != null) && this.Maximum.HasValue && (this.Maximum.Value != DateTime.MaxValue))
                {
                    DateTime maxDate = this.Maximum.Value;
                    _calendar.BlackoutDates.Add(new CalendarDateRange(maxDate.AddDays(1), DateTime.MaxValue));
                }
            }
        }

        #endregion //Methods
    }
}